/*
 * shell.c
 *
 *  Created on: 2016年11月23日
 *      Author: chenshisheng
 */

#include "shell.h"
#include "message_queue.h"
#include "stream/chprintf.h"
#include "stream/io_stream.h"
#include "utils.h"
#include "serial.h"
#include "version.h"
#include "ctimer.h"

#include <ctype.h>
#include <mstdio.h>
#include <string.h>
#include <stdlib.h>

// 命令列表
static const Shell_Cmd_t *_cmdList;

// 命令列表长度
static volatile unsigned int _listSize;

#ifdef CMSIS_OS
static osMessageQueueId_t _osMsgQue;
#endif

static struct Watch
{
    int argc;
    char *argv[16];
    void *stream;
    char buf[64];
    Ctimer_t timer;
} _watch;

/**
 * @brief 命令解释器初始化
 * @param cmdList 命令数组
 * @param size    命令数组长度
 */
void Shell_Init(void)
{
    extern unsigned int _shell_cmds_start, _shell_cmds_end; // 来自链接脚本
    const Shell_Cmd_t *start, *end;

    start = (const Shell_Cmd_t *) & _shell_cmds_start;
    end = (const Shell_Cmd_t *) & _shell_cmds_end;
    _cmdList = start;
    _listSize = end - start;

#ifdef CMSIS_OS
    _osMsgQue = osMessageQueueNew(8, sizeof(Stream_LineSignal_t), NULL);
#else
    mprintf("\n" MODEL "> ");
#endif
}

int Shell_SendLineSignal(const Stream_LineSignal_t *sig)
{
#ifdef CMSIS_OS
    int ret;

    ret = osMessageQueuePut(_osMsgQue, sig, 0, 0);
#ifdef SEND_TRY_BEST
    if(ret != 0)
    {
        ret = MsgQueue_Send(MsgQueue_Id_ShellLineIn, sig, sizeof(*sig));
    }
#endif // SEND_TRY_BEST

    return ret;
#else
    return MsgQueue_Send(MsgQueue_Id_ShellLineIn, sig, sizeof(*sig));
#endif // CMSIS_OS
}

#ifdef CMSIS_OS
int Shell_RecvOSLineSignal(Stream_LineSignal_t *sig)
{
    return osMessageQueueGet(_osMsgQue, sig, NULL, osWaitForever);
}
#endif

/**
 * @brief 以空白字符（\r \t \n \v等）分割字符串，分割出的子串作为argv数组的元素
 * @param str 待分割的字符串
 * @param argv 容纳分割出来的子串的数组
 * @param argcMax 可分割出的子串的最大数目，即argv数组的长度
 * @return  分割出的子串的数目
 */
static int _CmdStringSplit(char *str, char *argv[], int argcMax)
{
    int argc;
    bool argStarted;

    argStarted = FALSE;
    for(argc = 0; argc < argcMax; str++)
    {
        if(*str == '\0')
        {
            if(argStarted)
            {
                argc += 1;
            }

            break;
        }

        if(isspace((unsigned char)(*str)))
        {
            if(argStarted)
            {
                *str = '\0';
                argc += 1;
                argStarted = FALSE; // 开始获取下一arg
            }
        }
        else
        {
            if(! argStarted)
            {
                argv[argc] = str;
                argStarted = TRUE;
            }
        }
    }

    return argc;
}

static int _ExecCmd(void *stream, int argc, char *argv[])
{
    int ret = 1;
    unsigned int i;
    bool cmdExist;

    if(argc > 0)
    {
        cmdExist = FALSE;
        for(i = 0; i < _listSize; i++)
        {
            if(strcmp(argv[0], _cmdList[i].name) == 0)
            {
                cmdExist = TRUE;
                Stream_Put(stream, '\n');
                ret = _cmdList[i].callback(stream, argc, argv);
                break;
            }
        }

        if(! cmdExist)
        {
            chprintf(stream, "\nUnknown command: %s\n", argv[0]);
        }
    }

    return ret;
}

/**
 * @brief 试图从一行字符串中解释出命令名，并执行该命令
 * @param sig Stream_LineSignal_t信号
 */
void Shell_OnLine(Stream_LineSignal_t *sig)
{
    char line[64];
    int argc;
    char *argv[16];

    if(Stream_ReadUntil(sig->stream, line, sizeof(line), sig->tail) != 0)
    {
        chprintf(sig->stream, "Shell: Input Error!\n");
        return;
    }

    argc = _CmdStringSplit(line, argv, ARRAY_LEN(argv));
    _ExecCmd(sig->stream, argc, argv);

//    HAL_Delay(500);
    chprintf(sig->stream, MODEL "> ");
    Stream_Flush(sig->stream);
}

void Shell_Help(void *stream, const char *cmdName)
{
    unsigned int i;
    bool cmdExist = FALSE;

    for(i = 0; i < _listSize; i++)
    {
        if(strcmp(cmdName, _cmdList[i].name) == 0)
        {
            chprintf(stream, "%s\n\nUsage: %s %s\n", _cmdList[i].brief, cmdName, _cmdList[i].usage);
            cmdExist = TRUE;
            break;
        }
    }

    if(! cmdExist)
    {
        chprintf(stream, "Unknown command: %s\n", cmdName);
    }
}

/**
 * @brief 测试命令，打印参数
 * @param argc 命令参数数目
 * @param argv 命令参数数组
 * @return
 */
static int _CmdTest(void *stream, int argc, char *argv[])
{
    int i;
    extern void cm_backtrace_fault_test_by_unalign(void);
    extern void cm_backtrace_fault_test_by_div0(void);

    chprintf(stream, "Shell Test:\n");
    for(i = 0; i < argc; i++)
    {
        chprintf(stream, "arg %d: %s\n", i, argv[i]);
    }

    if(argc >= 3)
    {
        if(strcmp(argv[1], "backtrace") == 0)
        {
            if(strcmp(argv[2], "unalign") == 0)
            {
                cm_backtrace_fault_test_by_unalign();
            }
            else if(strcmp(argv[2], "div0") == 0)
            {
                cm_backtrace_fault_test_by_div0();
            }
        }
    }

    return 0;
}

SHELL_CMD(test, _CmdTest, "For shell test.", "[<arg 1>] [<arg 2>]...[<arg n>]");

/**
 * @brief 帮助命令，打印支持的命令名
 * @param argc 命令参数数目
 * @param argv 命令参数数组
 * @return
 */
static int _CmdHelp(void *stream, int argc, char *argv[])
{
    unsigned int i;

    if(argc == 1)
    {
        for(i = 0; i < _listSize; i++)
        {
            chprintf(stream, "%s\t - %s\n", _cmdList[i].name, _cmdList[i].brief);
        }
    }
    else if(argc >= 2)
    {
        Shell_Help(stream, argv[1]);
    }

    return 0;
}

SHELL_CMD(help, _CmdHelp, "Shows shell command help message.", "[<command name>]");

static int _OnWatchTimer(void *arg)
{
    struct Watch *watch = arg;

    return _ExecCmd(watch->stream, watch->argc, watch->argv);
}

static int _CmdWatch(void *stream, int argc, char *argv[])
{
    float fSec;
    char *buf;
    struct Watch *watch = &_watch;

    if(argc == 1)
    {
        Ctimer_Stop(&watch->timer);
        chprintf(stream, "Watch: stop\r\n");
    }
    else if(argc < 3)
    {
        Shell_Help(stream, argv[0]);
    }
    else
    {
        fSec = atoff(argv[1]);
        if(fSec <= 0)
        {
            Shell_Help(stream, argv[0]);
        }
        else if(_ExecCmd(stream, argc - 2, & argv[2]) == 0)
        {
            watch->stream = stream;
            watch->argc = argc - 2;
            buf = watch->buf;
            for(int i = 0; i < (argc - 2); i++)
            {
                strcpy(buf, argv[i + 2]);
                watch->argv[i] = buf;
                buf += strlen(argv[i + 2]) + 1;
            }

            unsigned int ms = (unsigned int)(CTIMER_TICK_SECOND(1) * fSec);
            Ctimer_Start(&watch->timer, ms, _OnWatchTimer, 0, watch);
        }
    }

    return 0;
}

SHELL_CMD(watch, _CmdWatch, "execute a program periodically", "[<second(float)> <command>]");
